

import java.util.*;
import java.io.*;

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;

public class PictureManager implements Runnable {
		private class PictureRequest{
			String _req = null;			// the content of the message
			int _numberOfFragments;		// number of fragments 
			
			/**
			 * constructor
			 * @param msg
			 * @param numberOfFragments
			 */
			public PictureRequest(String msg, int numberOfFragments){
				_req = msg;
				_numberOfFragments = numberOfFragments;
			}
			
			// get functions //
			public String getMessage(){ return _req; }
			
			public int getNumberOfFragments() { return _numberOfFragments; }
		}

		private class PeerPicture{
			private Record _peer = null;					// the entry of the current peer that we want to achieve its picture  
			private List<RequestSource> _sources = null;    // peers that have the new version of the picture
			private List<String> _data = null;				// array for the arriving data (fragments)		
			//private Date _picVersion = null;				// picture version
			private int _memAllocated;						// allocated memory 
			private long _lastConnection;					// the last time that data was received
			private int _fragmentsArrived;					// number of fragments that was arrived
			private int _expectedFragments;
			
			
			/**
			 * Constructor
			 * @param peer
			 * @param source
			 */
			public PeerPicture(Record peer, RequestSource source){
				_peer = peer;
				_stopFlag = false;
				_sources = new ArrayList<RequestSource>();
				_sources.add(source);
				_expectedFragments = Utils.getNumberOfFragments(_peer.get_picSize(), _mss);
				_data = new ArrayList<String>();
				for (int i=0; i<_expectedFragments + 1; ++i)
					_data.add(i, "-1");
				_memAllocated = 0;
				_fragmentsArrived = 0 ;
				_lastConnection = System.currentTimeMillis();
				getPicture();
			}
			
			/**
			 * Sends picture requests to different relevant sources
			 * according to the status of the receiving process.  
			 */
			private void getPicture(){
				// generate request picture messages
				Queue<PictureRequest> requests = messagesGenerator();
				
				// send requests
				while (!requests.isEmpty()){
					
					// if the free place in the buffer is less than 1 MSS - don't send request
					if (_buffer < _mss)
						// end operation
						return;
					
					PictureRequest req = requests.poll();
					
					// choose random source (1-N)
					int index = Utils.getRandomInteger(_nParam);
					
					// if source doesn't exist
					if (index >= _sources.size())
						// end operation
						continue;
					
					// if source exist - the program will send request to the chosen peer
					RequestSource source = _sources.get(index);
					
					// set receive window //
					int rcvWin = 0;
					int amountOfdata = req.getNumberOfFragments() * _mss;
					if (_buffer > amountOfdata)
						rcvWin = amountOfdata;
					else
						rcvWin = _buffer;
					
					// update buffer available space
					_buffer-=rcvWin;
					
					// update allocate memory counter
					_memAllocated += rcvWin;
					
					// create request message
					byte[] data = null;
					if (req.getMessage().equals(" "))
						 data = ("requestpicture<$dbversion=" + Utils.DateToString(source.getExistingDbVersion()) + ",$rcvWin=" 
						 + String.valueOf(rcvWin) + ",$peerName=" + _peer.get_peerName() + ">").getBytes();
					else
						data = ("requestpicture<$dbversion=" + Utils.DateToString(source.getExistingDbVersion())
								+ ",$rcvWin=" + String.valueOf(rcvWin) + ",$peerName=" + _peer.get_peerName() 
								 + "," + req.getMessage() + ">").getBytes();
					
					// check that socket is alive
					if (source.getSource().getSocket().isAlive())
						// send request message
						source.getSource().getSocket().send(new Message(data, source.getSource().getIP(), source.getSource().getPort(), source.getPeerName()));
				
				}		
			}
	
			/**
			 * Generates sub picture requests according to the status of the receiving process.  
			 * @return - Queue of picture requests.
			 */
			private Queue<PictureRequest> messagesGenerator(){
				Queue<PictureRequest> requests = new LinkedList<PictureRequest>();
				
				// if no fragment was arrived yet
				if (_fragmentsArrived == 0){
					// set request picture
					PictureRequest request = new PictureRequest(" ", _data.size() - 1);
					requests.add(request);
					return  requests;
				}
					
				int left = 1;
				int right = 1;
				int i = 1;
				
				while (i < _data.size()){				
					// if the current fragment was arrived
					if (!_data.get(i).equals("-1")){
						++i;
					} else { // the current fragment wasn't arrived
						left = i;
						right = left;
						int j = i + 1;
												
						// find the right border
						while (j < _data.size()){
							if (!_data.get(j).equals("-1")){
								// if only one fragment is missing
								if (right == left)
									requests.add(new PictureRequest("$list(" + String.valueOf(left) + ")",1));
								else { // right - left
									requests.add(new PictureRequest("$range(" + String.valueOf(left) + "-" + String.valueOf(right) + ")", right - left + 1));
								}
								break;
							}
							right = j;
							++j;
						}
						
						if (j == _data.size() && _data.get(j-1).equals("-1")){
							right = j-1;
							// generate message from right - left
							if (right == left)
								requests.add(new PictureRequest("$list(" + String.valueOf(left) + ")", 1));
							else
								requests.add(new PictureRequest("$range(" + String.valueOf(left) + "-" + String.valueOf(right) + ")", right - left + 1));
						}
						i = right + 1;
					}
				}
				
				return requests;	
			}
			
			public synchronized boolean fragmentWasArrived(int sn, int n, Date picVersion, String data ){
				// fragment version doesn't fit to expected version
				if (!picVersion.equals(_peer.get_picVersion()))
					// more fragments are expected
					return true;
				
				// wrong serial number
				if (n != _data.size()-1 || sn == 0)
					// more fragments are expected
					return true;
				
				// update last Connection time
				_lastConnection = System.currentTimeMillis();
				
				// check if duplicate packet arrived
				if (!_data.get(sn).equals("-1"))
					return true;
					
				// add data to the array
				_data.set(sn, data);
				// update fragments counter
				++_fragmentsArrived;
				
				if (_buffer < _originalBuffer){
					// free buffer
					_buffer += data.length();
					_memAllocated -= data.length();
				}
				
				// if all fragments was arrived
				if (_fragmentsArrived == _expectedFragments){
					// free all reserve memory
					_buffer += _memAllocated;
					_memAllocated = 0;
					
					//save picture to disk
					FileOutputStream out = null;
					try{
						// open destination file
						out = new FileOutputStream(_peer.get_peerName() + ".jpg");
						
						// write decoded data to file
						for (int i=1; i<_data.size(); ++i)
							out.write(HexBin.decode(_data.get(i)));
						
						// flush and close file
						out.flush();
						out.close();
						
					}catch (IOException ex) {}
					
					// save peer to DB
					_db.addRecord(_peer.get_peerName(), _peer);
					
					// indicate to PictureManage that no more fragments are expected
					return false;
				}
				// more fragments are expected
				return true;
				
			}
			
			// get / set functions //
			
			public synchronized Record getPeer(){ return _peer; }
			
			public synchronized void setPeer(Record peer){ _peer = peer; }
			
			/**
			 * Retrieves new source from which we can receive the current picture.
			 * @param source
			 */
			public synchronized void addSource(RequestSource source){
		
				// check if source already exist
				for (Iterator<RequestSource> itr = _sources.iterator(); itr.hasNext();){
					RequestSource s = itr.next();
					// delete the source
					if (s.getPeerName().equals(source.getPeerName()))
						itr.remove();
						
				}
									
				_sources.add(source);
					
			}
		
			/**
			 * Updates sources communication information and delete offline sources 
			 */
			private void updateSources(){
				for (Iterator<RequestSource> itr = _sources.iterator(); itr.hasNext();){
					RequestSource source = itr.next();
					Source currentSession = SessionsManager.getSocket(source.getPeerName());
					// if source is offline
					if (currentSession == null)
						itr.remove();
					// if communication details were updated
					else if (!source.getSource().equals(currentSession))
							source.setSource(currentSession);
				}
			}
			
			/**
			 * Check the status of the current picture receiving process. 
			 */
			public synchronized void status(){
				// check if max idle time already passed
				if (System.currentTimeMillis() - _lastConnection < _maxIdleTime)
					// data is still arriving
					return;
				
				// free allocated memory
				_buffer += _memAllocated;
				_memAllocated = 0;
				
				// update existing sources with the current communication information (IP, port, socket)
				updateSources();
				
				// generate sub-requests
				getPicture();
				
				// set new last connection time
				_lastConnection = System.currentTimeMillis();
			}
		}
	
		private int _buffer; 								// program receive buffer
		private int _originalBuffer;
		private Map<String,PeerPicture>  _pictures = null;	// picture requests hash map
		private long _maxIdleTime;							// maximum idle time allowed
		private DB _db;										// application's DB
		private int _nParam;								// N parameter
		private int _mss;									// MSS parameter		
		private boolean _stopFlag;
		
		/**
		 * Constructor
		 * @param db
		 * @param nParam
		 * @param mss
		 * @param buffer
		 * @param maxIdleTime
		 */
		public PictureManager(DB db, int nParam, int mss, int buffer, long maxIdleTime){
			_db = db;
			_nParam = nParam;
			_mss = mss;
			_maxIdleTime = maxIdleTime;
			_pictures = new HashMap<String, PeerPicture>();
			_buffer = buffer;
			_originalBuffer = _buffer;
			
		}
		
		/**
		 * Creates new picture receiving job if needed.
		 * @param peer
		 * @param source
		 */
		public synchronized void requestPicture(Record peer, RequestSource source){
			// check if request for the current peer already exists
			PeerPicture picReq = _pictures.get(peer.get_peerName());
			
			// if no request for the new peer exists
			if (picReq == null){
				// create new request
				picReq = new PeerPicture(peer,source);
				
				// insert new request to the hash map
				_pictures.put(peer.get_peerName(), picReq);
				
				return;
			}
			
			// a picture request for the new peer already exists //
			
			// check if the new peer version is newer than the current peer version
			Date currentPeerVersion = picReq.getPeer().get_peerVersion();
			Date currentPicVer = picReq.getPeer().get_picVersion();
			if (peer.get_peerVersion().compareTo(currentPeerVersion) == 1){// the new peer is updater than the current peer
				
				
				// if the new peer has a newer picture version
				if (peer.get_picVersion().compareTo(currentPicVer) == 1){
					// start new picture request job for the new version discarding the old request
					PeerPicture newReq = new PeerPicture(peer,source);
					_pictures.put(peer.get_peerName(), newReq);
				}
				else{ // the peer version is newer but the picture version is the same
					// update peer record in the request
					picReq.setPeer(peer);
					
					// add the new source to the list
					picReq.addSource(source);
				}
					
			}
			else if (peer.get_peerVersion().compareTo(currentPeerVersion) == 0 
					&& peer.get_dbVersion().compareTo(picReq.getPeer().get_dbVersion()) == 1){
				
				// update peer record in the request
				picReq.setPeer(peer);
				
				// add the new source to the list
				picReq.addSource(source);
				
			}
			else if (peer.get_picVersion().equals(currentPicVer)){ // if the picture version of the new peer is identical to the current picture version
					
				
					// add the current source as a source for the current picture
					picReq.addSource(source);
			}
		}

		public synchronized void handle(Message msg){
			// parse the message
			String fields[] = Utils.parseMessage(new String(msg.getData()));
			
			// get the relevant request from the hash map
			PeerPicture req = _pictures.get(fields[3]);
			
			// if no data on this peer is expected
			if (req == null)
				return;
			
			// update the relevant request
			boolean result = req.fragmentWasArrived(Integer.parseInt(fields[0]), Integer.parseInt(fields[1])
					, Utils.StringToDate(fields[2]), fields[4]);
			
			// if all expected fragments was arrived
			if (!result)
				// remove request picture job
				_pictures.remove(fields[3]);
		}
		
		/**
		 * Check if input peer is being handled by this object.
		 * @param peer
		 * @return - if true, returns peer's updated record.
		 */
		public synchronized Record isProcessingPeer(String peer){ 
			PeerPicture req = _pictures.get(peer);
			if (req == null)
				return null;
			
			return req.getPeer();
			}
		
		/**
		 * Check the status of all existing jobs
		 */
		private synchronized void status(){
			Set<String> keys = _pictures.keySet();
			for (String key : keys)
				_pictures.get(key).status();	
		}
		
		/**
		 * Run cyclic status in different thread 
		 */
		public void run(){
			try{
				while (!_stopFlag){
					Thread.sleep(50);
					status();
				}
			}
			catch (InterruptedException ex) {}
		}
		
		public void start(){
			new Thread(this,"Picture Manager Status").start();
		}
		
		public synchronized void stop(){
			_stopFlag = true;
		}
}
